#define _LARGEFILE64_SOURCE 
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/epoll.h>
#include <pthread.h>
#include <signal.h>
#include "custom_module_uapi.h"

#define OPER_STR_LEN 20
#define BUFFER_MAX_LEN 1024


void *epoll_thread_func(void *args)
{
    int fd = *(int*)args;    
    int epollFd = epoll_create(1);
    
    if (epollFd <= 0) {
        printf("epoll_create failed.%s\n", strerror(errno));
        return NULL;
    }
    struct epoll_event e;
    // 设置边沿触发模式
    e.events = EPOLLIN | EPOLLET;
    if (epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &e) == -1) {
        printf("epoll_ctl add fd failed.%s.fd=%d\n", strerror(errno), fd);
        close(epollFd);
        return NULL;
    }

    struct epoll_event event;
    char data[64];
    int ret;
    while (1) {
        ret = epoll_wait(epollFd, &event, 1, 2000);
        if (ret == -1) {
            printf("epoll error.%s\n", strerror(errno));
            return NULL;
        } else if (ret == 0) {
            printf("epoll timeout\n");
            return NULL;
        } else {
            memset(data, 0, 64);
            lseek64(fd, 0, SEEK_SET);
            if (read(fd, data, 64) == -1) {
                printf("read error.%s\n", strerror(errno));
            } else {
                printf("epoll read:%s\n", data);
            }
        }
    }

    close(epollFd);
    return NULL;
}

void signal_callback(int signum)
{
    printf("recevie signal from kernel.signum = %d\n", signum);
}

int main(int argc, char *argv[])
{
    printf("argv[1]:%s\n", argv[1]);
    int fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        printf("open device %s failed.%s\n", argv[1], strerror(errno));
        return -1;
    }

    fcntl(fd, F_SETOWN, getpid());/* 将当前进程的进程号告诉给内核 */
    int flag = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flag | FASYNC);     // 启动异步通知
    signal(SIGIO, signal_callback);

    pthread_t epoll_thread = 0;
    char operation[OPER_STR_LEN];
    char yesNo[OPER_STR_LEN];
    char buf[BUFFER_MAX_LEN];
    char flags[OPER_STR_LEN];
    size_t len;
    ssize_t bytes;
    off64_t offset;
    unsigned long cmd;
    unsigned long long maxSize;
    bool isStartPolling;
    while (1) {
        memset(operation, 0 ,OPER_STR_LEN);
        printf("operation(read/write/lseek/mmap/ioctl/epoll/quit):");
        scanf("%s", operation);
        if (!strncmp(operation, "read", strlen("read"))) {
            printf("read length[length]:");
            scanf("%lu", &len);
            memset(buf, 0, BUFFER_MAX_LEN);
            bytes = read(fd, buf, len < BUFFER_MAX_LEN ? len : BUFFER_MAX_LEN);
            if (bytes == -1) {
                printf("read error.%s\n", strerror(errno));
            } else {
                printf("read %d bytes:%s\n", bytes, buf);
            }
        } else if (!strncmp(operation, "write", strlen("write"))) {
            memset(buf, 0, BUFFER_MAX_LEN);
            printf("write[data]:");
            scanf("%s", buf);            
            bytes = write(fd, buf, strlen(buf));
            if (bytes == -1) {
                printf("write error.%s\n", strerror(errno));
            } else {
                printf("write %d bytes:%s\n", bytes, buf);
            }
        } else if (!strncmp(operation, "lseek", strlen("lseek"))) {
            memset(flags, 0, OPER_STR_LEN);
            printf("lseek flags[type,offset]:");
            scanf("%s %d", flags, &offset);
            printf("offset %d\n", offset);
            if (!strncmp(flags, "SEEK_SET", strlen("SEEK_SET"))) {
                offset = lseek64(fd, offset, SEEK_SET);         
            } else if (!strncmp(flags, "SEEK_END", strlen("SEEK_END"))) {
                offset = lseek64(fd, offset, SEEK_END);
            } else if (!strncmp(flags, "SEEK_CUR", strlen("SEEK_CUR"))) {
                offset = lseek64(fd, offset, SEEK_CUR);
            } else {
                printf("unsupport lseek flags:%s\n", flags);
                continue;
            }
            if (offset == -1) {
                printf("lseek error.%s\n", strerror(errno));
            } else {
                printf("reset offset to %d\n", offset);
            }            
        } else if (!strncmp(operation, "quit", strlen("quit"))) {
            printf("finish interconnect program\n");
            break;
        } else if (!strncmp(operation, "mmap", strlen("mmap"))) {
            printf("mmap [size]:");
            scanf("%lu", &len);
            char *data = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
            if (data == -1) {
                printf("mmap error.%s\n", strerror(errno));            
            } else {
                printf("kernel data:%s\n", data);
                munmap(data, len);
            }
        } else if (!strncmp(operation, "ioctl", strlen("ioctl"))) {
            printf("ioctl cmd [cmd](0/1/2):");
            scanf("%lu", &cmd);
            switch (cmd) {
                case 0:
                    if (ioctl(fd, CUSTOM_MODULE_IOCTL_GET_SIZE_CMD, &maxSize)) {
                        printf("ioctl get maxSize failed\n");                                         
                    } else {
                        printf("get module maxSize:%llu\n", maxSize);
                    }
                    break;
                case 1:
                    printf("Start Polling?[y/n]\n");
                    memset(yesNo, 0, OPER_STR_LEN);
                    scanf("%s", yesNo);
                    isStartPolling = !strncmp(yesNo, "y", strlen("y"));
                    if (ioctl(fd, CUSTOM_MODULE_IOCTL_SET_TIMER, &isStartPolling)) {
                        printf("ioctl set polling failed\n");                                         
                    }
                    break;
                case 2:
                    if (ioctl(fd, CUSTOM_MODULE_IOCTL_KILL, 0)) {
                        printf("ioctl send signal failed.%s\n", strerror(errno));
                    }
                    break;
                default:
                    printf("unsupported ioctl cmd\n");
            }
        } else if (!strncmp(operation, "epoll", strlen("epoll"))) {
            if (epoll_thread) {
                if (!pthread_kill(epoll_thread, 0)) {
                    pthread_cancel(epoll_thread);
                } else {
                    printf("epoll_thread %u not running\n", epoll_thread);
                }
                epoll_thread = 0;
            }
            pthread_attr_t attr;
            pthread_attr_init(&attr);
            pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
            printf("input argument fd:%d\n", fd);
            if (pthread_create(&epoll_thread, &attr, epoll_thread_func, &fd)) {
                printf("create epoll thread failed\n");
            }
            pthread_attr_destroy(&attr);
        }
    }

    close(fd);
    return 0;
}